package cn.itcast.dewu.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.itcast.dewu.dto.Result;
import cn.itcast.dewu.dto.ScrollResult;
import cn.itcast.dewu.dto.UserDTO;
import cn.itcast.dewu.pojo.Blog;
import cn.itcast.dewu.pojo.Follow;
import cn.itcast.dewu.pojo.Goods;
import cn.itcast.dewu.pojo.User;
import cn.itcast.dewu.service.BlogService;
import cn.itcast.dewu.mapper.BlogMapper;
import cn.itcast.dewu.service.FollowService;
import cn.itcast.dewu.service.GoodsService;
import cn.itcast.dewu.service.UserService;
import cn.itcast.dewu.utils.RabbitConfirmCallbackUtil;
import cn.itcast.dewu.utils.RedisIdWork;
import cn.itcast.dewu.utils.UserHolder;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.queryparser.xml.builders.BooleanQueryBuilder;
import org.apache.lucene.search.BooleanQuery;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.sql.Struct;
import java.util.*;
import java.util.stream.Collectors;

import static cn.itcast.dewu.utils.ESConstants.PAGE_DEFAULT_SIZE;
import static cn.itcast.dewu.utils.RedisConstants.BLOG_LIKED_KEY;

/**
 * @author Administrator
 * @description 针对表【tb_blog】的数据库操作Service实现
 * @createDate 2022-04-29 14:05:02
 */
@Service
@Slf4j
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog>
        implements BlogService {


    @Autowired
    private UserService userService;
    @Autowired
    private GoodsService goodsService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisIdWork redisIdWork;
    @Autowired
    private FollowService followService;

    /*
     * 点赞*/
    @Override
    public Result likeBlog(Long id) {
//        获取一下当前登录用户
        String key = BLOG_LIKED_KEY + id;
        Long userid = UserHolder.getUser().getId();
        //从redis查询用户是否点过赞
        Double score = stringRedisTemplate.opsForZSet().score(key, userid.toString());
        if (score == null) {
            //未点过赞
            boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().add(key, userid.toString(), System.currentTimeMillis());
            } else {
                throw new RuntimeException();
            }
        } else {
            //点过赞
            boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
            if (isSuccess)
                stringRedisTemplate.delete(key);
        }
        return Result.ok();
    }

    /*
        查看所有播客
    * */
    @Override
    public Result queryHotBlog(Integer current) {
        //按交的钱的多少
        Page<Blog> page = query().orderByDesc("is_money")
                .orderByDesc("liked")
                .page(new Page<>(current, 6));
        //获取本业的数据
        List<Blog> records = page.getRecords();
        System.err.println(records);
        if (records.isEmpty()) {
            return Result.ok();
        }
        records.forEach(
                blog -> {
                    Long userId = blog.getUserId();
                    User user = userService.getById(userId);
                    blog.setName(user.getNickName()).setIcon(user.getIcon());
                    this.isBlogLiked(blog);
                }
        );

        return Result.ok(records);
    }


    /*
     * 查询某个播客的详细信息
     * */
    @Override
    public Result queryBlogById(Long id) {
        Blog blog = query().eq("id", id).one();
        if (blog == null) {
            return Result.fail("博客不存在^_^");
        }
        //获取一下当前播客的用户
        queryBlogUser(blog);
        //查询是否点过赞
        isBlogLiked(blog);

        return Result.ok(blog);
    }

    /*
     * 查询播客点赞的所有人 前五个人显示在主页上
     * */
    @Override
    public Result queryBlogLikes(Long id) {
        String key = BLOG_LIKED_KEY + id;
        Set<String> useridSet = stringRedisTemplate.opsForZSet().range(key, 0, 4);
        if (useridSet == null || useridSet.isEmpty()) {
            //不存在的话返回空集和
            return Result.ok(Collections.emptyList());
        }
        //解析出各个用户
        List<Long> userids = useridSet.stream()
                .map(Long::valueOf)
                .collect(Collectors.toList());
        String useridStr = StrUtil.join(",", userids);
        //查询数据库
        List<User> userList = userService.query().in("id", userids)
                .last("ORDER BY  FIELD(id," + useridStr + ")")
                .list();
        List<UserDTO> userDTOS = userList.stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.ok(userDTOS);
    }

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /*
     * 删除播客
     * */
    @Override
    @Transactional
    public Result deleteBlog(Long id) {
        boolean b = removeById(id);
        if (b) {
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString(true));
            correlationData.getFuture().addCallback(
                    result2 -> {
                        if (result2.isAck()) {
                            log.info("消息发送成功,消息id为:{}", correlationData.getId());
                        } else {
                            log.error("消息发送到交换机失败,消息的id为:{},发送失败的原因为:{}"
                                    , correlationData.getId(), result2.getReason());
                            //重新发送消息
                            rabbitTemplate.convertAndSend("delete.blog.exchange",
                                    "delete.blog", id, correlationData);
                        }
                    },
                    ex -> {
                        log.error("消息发送异常,消息的id为:{},发送失败的原因为:{}"
                                , correlationData.getId(), ex.getMessage());
                        //重新发送消息
                        rabbitTemplate.convertAndSend("delete.blog.exchange",
                                "delete.blog", id, correlationData);
                    }
            );
            //发送消息
            rabbitTemplate.convertAndSend("delete.blog.exchange",
                    "delete.blog", id, correlationData);
        }
//        RabbitConfirmCallbackUtil.defineRabbitConfirmCallback(id,"delete.blog.exchange","delete.blog");
        return Result.ok("删除成功^_^");

    }
    /*
     * 新增播客
     * */

    @Override
    public Result saveBlog(Blog blog) {
        //获取到当前登录的用户
        Long id = UserHolder.getUser().getId();
        blog.setUserId(id);
        blog.setId(redisIdWork.nextID("blog:id:"));
        boolean save = save(blog);
        if (!save) {
            return Result.fail("上传播客失败,请重新上传^_^");
        }
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString(true));
        correlationData.getFuture().addCallback(
                result2 -> {
                    if (result2.isAck()) {
                        log.info("消息发送成功,消息id为:{}",correlationData.getId());
                    }else{
                        log.error("消息发送到交换机失败,消息的id为:{},发送失败的原因为:{}"
                                ,correlationData.getId(),result2.getReason());
                        //重新发送消息
                        rabbitTemplate.convertAndSend("blog.save.exchange",
                                "blog.save",blog,correlationData);
                    }
                },
                ex->{
                    log.error("消息发送异常,消息的id为:{},发送失败的原因为:{}"
                            ,correlationData.getId(),ex.getMessage());
                    //重新发送消息
                    rabbitTemplate.convertAndSend("blog.save.exchange",
                            "blog.save",blog,correlationData);
                }
        );
        //发送消息
        rabbitTemplate.convertAndSend("blog.save.exchange",
                "blog.save",blog,correlationData);
        //查询播客本人的所有粉丝
        List<Follow> follows = followService.query().eq("follow_user_id", id).list();
        if (follows.isEmpty()) {
            return Result.ok();
        }
        follows.forEach(
                follow -> {
                    //获取粉丝的id
                    Long userId = follow.getUserId();
                    //进行推送
                    String key = "feed:" + userId;
                    stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
                }
        );
        return Result.ok(blog.getId());
    }

    /*
     * 给粉丝推送消息
     * */
    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        //获取当前用户
        Long userId = UserHolder.getUser().getId();
        //从redis中查询粉丝收件箱
        String key = "feed:" + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples =
                stringRedisTemplate.opsForZSet()
                        .rangeByScoreWithScores(key, 0, max, offset, 6);
        //用于存放播客的id
        if (typedTuples.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }
        List<Long> blogIdList = new ArrayList<>();
        long mintime = 0L;
        Integer os = 1; //初始化offset  offset = 重复的时间戳的个数
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) { //54432111
            long time = typedTuple.getScore().longValue(); //获取到时间戳
            if (time == mintime) {
                os++;
            } else { //获取的时间不是当前时间 就需要把新取得时间赋值 将os重新置1
                mintime = time;
                os = 1;
            }
            String blogid = typedTuple.getValue();//这是播客id
            blogIdList.add(Long.valueOf(blogid));
        }
        String idStr = StrUtil.join(",", blogIdList);
        //查询所有的博客
        List<Blog> blogs = query().in("id", blogIdList)
                .last("ORDER BY FIELD(id," + idStr + ")")
                .list(); //按顺序查出来
        //返回
        blogs.forEach(
                blog -> {
                   this.queryBlogUser(blog);
                    this.isBlogLiked(blog);
                }

        );

        ScrollResult result = new ScrollResult();
        result.setList(blogs).setOffset(os).setMinTime(mintime);
        return Result.ok(result);
    }

    /*
     *
     *
     * 这里是根据
     *
     * */
    @Resource
    private RestHighLevelClient client;

    @Override
    public Result queryblogByContent(Integer corrent, String content) {
        try {
            //准备请求
            SearchRequest request = new SearchRequest("blog_index");
            int from = (corrent - 1) * PAGE_DEFAULT_SIZE;
            //布尔查询
            BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();
            //搜索功能
           booleanQuery.must(QueryBuilders.matchQuery("all", content));
            //进行加分处理 ,投的钱越多越容易上热门
            FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                    QueryBuilders.matchQuery("all", content),//原始查询
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    QueryBuilders.rangeQuery("isMoney").gt(0),
                                    ScoreFunctionBuilders.fieldValueFactorFunction("isMoney")
                            )
                    }
            );

            request.source().query(functionScoreQuery);
            //高亮显示
            request.source().highlighter(new HighlightBuilder()
                    .field("content").field("title").requireFieldMatch(false));
            //分页功能
            request.source().from(from).size(PAGE_DEFAULT_SIZE);
            //发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return handleResponse(response);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            /*try {
                if (client != null)
                    client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }*/
        }
    }

    private Result handleResponse(SearchResponse response) {
        //获取hits
        SearchHits hits = response.getHits();
        //总条数
        long total = hits.getTotalHits().value;
        //再次获取hits集合
        SearchHit[] hitsHits = hits.getHits();
        List<Blog> blogs = new ArrayList<>();
        //进行遍历
        for (SearchHit hit : hitsHits) {
            //获取_source
            String goodsJson = hit.getSourceAsString();
            //进行反序列化 非高亮的
            Blog blog = JSONUtil.toBean(goodsJson, Blog.class);
            //处理高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields.isEmpty() || highlightFields != null) {
                //对内容以及标题进行高亮获取高亮结果
                HighlightField field = highlightFields.get("content");
                HighlightField field2 = highlightFields.get("title");
                if (field != null) {
                    String hName = field.getFragments()[0].toString();
                    blog.setContent(hName);
                }
                if (field2 != null) {
                    String hName = field2.getFragments()[0].toString();
                    blog.setTitle(hName);
                }
            }
            //放入到集合中
            blogs.add(blog);
        }
        //返回总条数 以及blog的集合
        return Result.ok(blogs, total);
    }


    /*
     * 查看当前登录用户是否点过赞
     * */
    private void isBlogLiked(Blog blog) {
        //判断当前用户是否点赞
        Long userID = UserHolder.getUser().getId();
        //判断当前登录用户是否点赞
        String key = BLOG_LIKED_KEY + blog.getId();
        //判断zset集合中是否有该用户
        Double score = stringRedisTemplate.opsForZSet().score(key, userID.toString());
        if (score == null) {
            blog.setIsLike(false);
            return;
        }
        blog.setIsLike(true);
    }

    /*
     * 获取一下当前播客的用户
     * */
    private void queryBlogUser(Blog blog) {
        //播客的用户id
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        Long goodsId = blog.getGoodsId();
        Goods goods = goodsService.getById(goodsId);
        blog.setName(user.getNickName()).setIcon(user.getIcon())
                .setGoodsIcon(goods.getImages()).setGoodsName(goods.getName());
    }
}